﻿using System;
using System.Collections.Generic;
using Loki.Messages;
using Loki.Resources;
using Loki.Utils;
using Spring.Context.Support;
using Spring.Objects.Factory.Xml;

namespace Loki.Engine
{
    /// <summary>
    /// Helper on spring context.
    /// </summary>
    public class SpringContext : LokiObject, IObjectContext
    {
        private GenericApplicationContext _SpringContext;

        private bool _ContextLocked = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="SpringContext"/> class.
        /// </summary>
        /// <param name="P_ContextName">Context name.</param>
        public SpringContext(string P_ContextName)
        {
            _SpringContext = new GenericApplicationContext();
            _SpringContext.Name = P_ContextName;

            if (!ContextRegistry.IsContextRegistered(_SpringContext.Name))
            {
                ContextRegistry.RegisterContext(_SpringContext);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SpringContext"/> class.
        /// </summary>
        /// <param name="P_ContextName">Context name.</param>
        /// <param name="P_BaseDefinitions">Base definitions URI.</param>
        public SpringContext(string P_ContextName, string P_BaseDefinitions)
        {
            try
            {
                _SpringContext = new GenericApplicationContext();
                _SpringContext.Name = P_ContextName;

                if (!ContextRegistry.IsContextRegistered(_SpringContext.Name))
                {
                    ContextRegistry.RegisterContext(_SpringContext);
                }

                LoadContext(P_BaseDefinitions);
            }
            catch (Exception Ex)
            {
                Log.Error(Ex.Message, Ex);
                throw;
            }
        }

        /// <summary>
        /// Loads the additional spring context.
        /// </summary>
        /// <param name="P_ObjectDefinitionURI">The object definition URI.</param>
        public void LoadContext(params string[] P_ObjectDefinitionURI)
        {
            XmlObjectDefinitionReader L_Reader = new XmlObjectDefinitionReader(_SpringContext);
            L_Reader.LoadObjectDefinitions(P_ObjectDefinitionURI);
        }

        /// <summary>
        /// Marks the context as loaded.
        /// </summary>
        public void EndLoad()
        {
            _ContextLocked = true;
            _SpringContext.Refresh();
        }

        /// <summary>
        /// Gets an instance of type <typeparamref name="T"/> as defined in the spring configuration.
        /// </summary>
        /// <typeparam name="T">The object type.</typeparam>
        /// <param name="P_ObjectName">Name of the spring object.</param>
        /// <returns>The object.</returns>
        public T GetObject<T>(string P_ObjectName)
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            if (_Builders.ContainsKey(P_ObjectName))
            {
                if (_SingletonsStore.ContainsKey(P_ObjectName))
                {
                    return (T)_SingletonsStore[P_ObjectName];
                }
                else
                {
                    T L_Item = (T)_Builders[P_ObjectName]();
                    if (_Singletons.Contains(P_ObjectName))
                    {
                        _SingletonsStore[P_ObjectName] = L_Item;
                    }

                    return L_Item;
                }
            }
            else if (_SpringContext.IsObjectNameInUse(P_ObjectName))
            {
                return _SpringContext.GetObject<T>(P_ObjectName);
            }
            else
            {
                throw BuildErrorFormat<ArgumentException>(ToolsErrors.TOO_402, P_ObjectName);
            }
        }

        /// <summary>
        /// Determines whether the object with the specified name is defined.
        /// </summary>
        /// <param name="P_ObjectName">Name of the object.</param>
        /// <returns>
        ///   Returns <c>true</c> if the object with the specified name is defined; otherwise, <c>false</c>.
        /// </returns>
        public bool IsDefined(string P_ObjectName)
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            return _SpringContext.IsObjectNameInUse(P_ObjectName) || _Builders.ContainsKey(P_ObjectName);
        }

        public void DefineType<T>(string P_Name, Func<T> P_Builder)
        {
            DefineType<T>(P_Name, P_Builder, true);
        }

        public void DefineType<T>(string P_Name, Func<T> P_Builder, bool P_Singleton)
        {
            if (_Builders.ContainsKey(P_Name))
            {
                Log.WarnFormat(LogStrings.Builder_SpringEngine_OverrindingDefinition, P_Name);
            }

            _Builders[P_Name] = () => P_Builder();
            if (P_Singleton)
            {
                _Singletons.Add(P_Name);
            }
        }

        public void DefineType<T>(string P_Name) where T : new()
        {
            DefineType<T>(P_Name, true);
        }

        public void DefineType<T>(string P_Name, bool P_Singleton) where T : new()
        {
            Type L_Type = typeof(T);
            if (!ExpressionHelper.HasDefaultConstructor(typeof(T)))
            {
                Log.ErrorFormat(LogStrings.Builder_SpringEngine_NoDefaultConstructor, L_Type.ToString());
            }
            else
            {
                DefineType<T>(P_Name, () => (T)L_Type.GetConstructor(Type.EmptyTypes).Invoke(null), P_Singleton);
            }
        }

        public void InjectSingleton<T>(string P_Name, T P_Instance)
        {
            if (_Builders.ContainsKey(P_Name))
            {
                Log.WarnFormat(LogStrings.Builder_SpringEngine_OverrindingDefinition, P_Name);
            }

            _SingletonsStore[P_Name] = P_Instance;
            _Builders.Add(P_Name, () => P_Instance);
            _Singletons.Add(P_Name);
        }

        #region IDisposable
        /// <summary>
        /// Releases all resources used by an instance of the <see cref="SpringContext" /> class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
        /// finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~SpringContext()
        {
            Dispose(false);
        }

        /// <inheritdoc/>
        protected virtual void Dispose(bool P_Disposing)
        {
            _SpringContext.Dispose();
            foreach (object L_Item in _SingletonsStore.Values)
            {
                IDisposable L_DisposableItem = L_Item as IDisposable;
                if (L_DisposableItem != null)
                {
                    L_DisposableItem.Dispose();
                }
            }

            _SingletonsStore.Clear();
        }
        #endregion

        private HashSet<string> _Singletons = new HashSet<string>();

        private IDictionary<string, object> _SingletonsStore = new Dictionary<string, object>();

        private IDictionary<string, Func<object>> _Builders = new Dictionary<string, Func<object>>();
    }
}
